package com.tasly.attendance.domain.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.tasly.attendance.domain.AttendanceAnalyse;
import com.tasly.attendance.domain.calc.CalculateFactory;
import com.tasly.attendance.domain.calc.Calculator;
import com.tasly.attendance.entity.AttendanceRecordEntity;
import com.tasly.attendance.entity.DayHoursEntity;
import com.tasly.attendance.util.AttendanceTimeComparator;
import com.tasly.attendance.util.AttendanceUtils;

public class AttendanceAnalyseImpl implements AttendanceAnalyse {
    
    @Override
    public Map<String, List<DayHoursEntity>> analyse(Map<String, List<AttendanceRecordEntity>> userRecordsMap, Date monthLimit) {
        // TODO Auto-generated method stub
        //prepare data before analyse
        prepareData(userRecordsMap, monthLimit);
        //start analyse
        //TODO: delete testing part
        Map<String, List<DayHoursEntity>> analysedMap = getAnalyseResult(userRecordsMap, monthLimit);
        return analysedMap;
        
    }

    @Override
    public Map<String, List<AttendanceRecordEntity>> prepareData(Map<String, List<AttendanceRecordEntity>> userRecordsMap, Date monthLimit) {
        //remove the invalid data that not in time range
        removeNotNeedData(userRecordsMap, monthLimit);
        //sort every person's attendance records according datetime before analyse.
        sortMap(userRecordsMap);
        return userRecordsMap;
    }
    
    /**
     * @param userRecordsMap
     */
    private void sortMap(
            Map<String, List<AttendanceRecordEntity>> userRecordsMap) {
        Collection<List<AttendanceRecordEntity>> values = userRecordsMap.values();
        for (List<AttendanceRecordEntity> list: values) {
            Collections.sort(list, new AttendanceTimeComparator());
        }
    }
    
    /**
     * remove the datetime not between lowerLimitDate and upperLimitDate
     * @param userRecordsMap
     * @param monthLimit
     */
    private void removeNotNeedData(Map<String, List<AttendanceRecordEntity>> userRecordsMap, Date monthLimit) {
        AttendanceRecordEntity AttendanceRecordEntityTmp = null;
        Collection<List<AttendanceRecordEntity>> values = userRecordsMap.values();
        Calendar calender = Calendar.getInstance();
        Date lowerLimitDate = monthLimit;
        calender.setTime(lowerLimitDate);
        calender.add(Calendar.MONTH, 1);
        Date upperLimitDate = calender.getTime();
        //remove the records which not between the upperLimitDate and lowerLimitDate
        for (List<AttendanceRecordEntity> list: values) {
            for (Iterator<AttendanceRecordEntity> iterator = list.iterator(); iterator.hasNext();) {
                AttendanceRecordEntityTmp = iterator.next();
                if (AttendanceRecordEntityTmp.getWorkingTime().before(lowerLimitDate)
                        || AttendanceRecordEntityTmp.getWorkingTime().after(
                                upperLimitDate)) {
                    iterator.remove();
                }
            }
        }
    }
    
    /**
     * core class
     * use to calculate the working hours
     * @param userRecordsMap
     * @param monthLimit
     * @return
     */
    private Map<String, List<DayHoursEntity>> getAnalyseResult(
            Map<String, List<AttendanceRecordEntity>> userRecordsMap, Date monthLimit){
        
        Map<String, List<DayHoursEntity>> userDateHoursMap = new HashMap<String, List<DayHoursEntity>>();
        Set<String> userIdSet = userRecordsMap.keySet();
        List<Date> dateCalculatePoolList = new ArrayList<Date>();
        Calendar calendar = Calendar.getInstance();
        
        for (String userId : userIdSet) {
            //make a copy before process one person's attendance
            // to make sure start with the first day of the supplied month
            Date copyedLimitDate = (Date)monthLimit.clone();
            //List below use to store the hours per day in month, key is the day of month
            List<DayHoursEntity> dayHoursList = new ArrayList<DayHoursEntity>();
            List<AttendanceRecordEntity> attendanceRecordList = userRecordsMap.get(userId);
            //TODO: refactor after development
            for (int i =0; i<attendanceRecordList.size(); i++) {
                //get upper limit date
                Date nextDate = AttendanceUtils.getNextDay(copyedLimitDate);
                AttendanceRecordEntity attendanceRecordEntity = attendanceRecordList.get(i);
                if (attendanceRecordEntity.getWorkingTime().after(copyedLimitDate)
                        && attendanceRecordEntity.getWorkingTime().before(nextDate)) {
                    dateCalculatePoolList.add(attendanceRecordEntity.getWorkingTime());
                }else {
                    // back to the previous reord if the condition is not true
                    i--;
                    //calculate working hours
                    double hours = hourCalculate(dateCalculatePoolList, copyedLimitDate);
                    //store the calculate result
                    calendar.setTime(copyedLimitDate);
                    int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
                    dayHoursList.add(new DayHoursEntity(dayOfMonth, hours));
                    //make start day equals to next day before goto next loop
                    //so the next day here is the start day in next loop
                    copyedLimitDate = nextDate;
                    //clear the calculate pool before next loop
                    dateCalculatePoolList.clear();
                }
            }
            //calculate the last day of the month
            if (dateCalculatePoolList.size() != 0) {
              //calculate working hours
                double hours = hourCalculate(dateCalculatePoolList, copyedLimitDate);
                //store the calculate result
                calendar.setTime(copyedLimitDate);
                int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
                dayHoursList.add(new DayHoursEntity(dayOfMonth, hours));
                dateCalculatePoolList.clear();
            }
            userDateHoursMap.put(userId, dayHoursList);
        }
        
        return userDateHoursMap;
    }
    
    /**
     * excpeiton occurred when return -1;
     * @param dateCalculatePoolList
     * @return
     */
    private double hourCalculate(List<Date> dateCalculatePoolList,Date dateLimit) {
        //TODO: try to refactor the code below, make configurable at least
        if (dateCalculatePoolList.size() == 0) {
            return 0;
        }
//        Date endDateTime = dateCalculatePoolList.get(dateCalculatePoolList.size() - 1);
//        Date startDateTime = dateCalculatePoolList.get(0);
//        double workingHours = (endDateTime.getTime() - startDateTime.getTime())/(60*60*1000);
        
//        if (endDateTime.equals(startDateTime)) {
//            //only one record, maybe forgot swing card
//            return -1;
//        } else if (workingHours < 7) {
//            //working hours not enough, need some investigation by hand
//            return -1;
//        }
        
        //3 mode hours will be return so far
        //12,11.5,8 hours
        double hours;
        if ((hours = CalculateFactory.getCalculator(
                CalculateFactory.NORMAL_HOURS_CALCULATOR_TYPE).calculate(
                dateCalculatePoolList, dateLimit)) != Calculator.INVALID_HOURS ) {
        } else if ((hours = CalculateFactory.getCalculator(
                CalculateFactory.EIGHT2EIGHT_HOURS_CALCULATOR_TYPE).calculate(
                dateCalculatePoolList, dateLimit)) != Calculator.INVALID_HOURS) {
        } else if ((hours = CalculateFactory.getCalculator(
                CalculateFactory.NIGHTLY_HOURS_CALCULATOR_TYPE).calculate(
                dateCalculatePoolList, dateLimit)) != Calculator.INVALID_HOURS) {
        } else if ((hours = CalculateFactory.getCalculator(
                CalculateFactory.RESTING_HOURS_CALCULATOR_TYPE).calculate(
                dateCalculatePoolList, dateLimit)) != Calculator.INVALID_HOURS) {
        }  else {
            return Calculator.INVALID_HOURS;
        }
        return hours;
    }

}
